﻿using System;
using System.Collections.Generic;
using System.Linq;
using CardPlay.Model.GameModel.Actions;

namespace CardPlay.Model.GameModel
{
    public class PlayingRules : IPlayingRules
    {
        private readonly List<LocationPolicy> _zonePolicies = new List<LocationPolicy>();
        private readonly LocationPolicy _gamePolicy = new LocationPolicy();

        public sealed class ActionConditions<TAction> : IPlayingRules where TAction : GameAction
        {
            public Predicate<TAction> IsAcceptable = delegate { return true; };

            bool IPlayingRules.Permit(GameAction action)
            {
                var requiredAction = action as TAction;
                return requiredAction != null && IsAcceptable(requiredAction);
            }

            public void When(Predicate<TAction> ruling)
            {
                IsAcceptable = ruling;
            }

            public void Never()
            {
                IsAcceptable = delegate { return false; };
            }
        }

        public sealed class LocationPolicy : IPlayingRules
        {
            private readonly Dictionary<Type, IPlayingRules> _actionPolicies = new Dictionary<Type, IPlayingRules>();

            public string Location { get; set; }

            public bool IsApplicableAt(PartLocation partLocation)
            {
                return String.IsNullOrEmpty(Location) || partLocation.Matches(Location);
            }

            public LocationPolicy ClearAll()
            {
                _actionPolicies.Clear();
                return this;
            }

            public LocationPolicy Revert<TAction>() where TAction : GameAction
            {
                _actionPolicies.Remove(typeof (TAction));
                return this;
            }

            public LocationPolicy ConditionallyPermit<TAction>(Predicate<TAction> isAcceptable) where TAction : GameAction
            {
                Permit<TAction>().When(isAcceptable);
                return this;
            }

            public ActionConditions<TAction> Permit<TAction>() where TAction : GameAction
            {
                IPlayingRules playingRules;

                if (!_actionPolicies.TryGetValue(typeof(TAction), out playingRules))
                {
                    playingRules = new ActionConditions<TAction>();
                    _actionPolicies[typeof (TAction)] = playingRules;
                }

                return (ActionConditions<TAction>) playingRules;
            }

            public bool Permit(GameAction action)
            {
                return IsApplicableAt(action.Location) && _actionPolicies.Any(pair => pair.Key.IsInstanceOfType(action) && pair.Value.Permit(action));
            }
        }

        public PlayingRules()
        {
            _zonePolicies.Add(_gamePolicy);
        }

        bool IPlayingRules.Permit(GameAction action)
        {
            return action != null && _zonePolicies.Any(policy => policy.Permit(action));
        }

        public LocationPolicy At(string locationSpecification)
        {
            var existingPolicy = _zonePolicies.Find(policy => policy.Location == locationSpecification);

            if (existingPolicy == null)
            {
                existingPolicy = new LocationPolicy { Location = locationSpecification };
                _zonePolicies.Insert(0, existingPolicy);
            }

            return existingPolicy;
        }

        public void Revert<TAction>() where TAction : GameAction
        {
            _gamePolicy.Revert<TAction>();
        }

        public ActionConditions<TAction> Permit<TAction>() where TAction : GameAction
        {
            return _gamePolicy.Permit<TAction>();
        }
    }
}
